package org.esir.base.android.camera;

import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class CameraWrapperImpl extends CameraWrapper implements Camera.PreviewCallback {
    private final String TAG = "CameraWrapperImpl";

    // event for Camera
    private final static int EVENT_OPEN_CAMERA = 1;
    private final static int EVENT_START_PREVIEW = 2;


    // worker thread for opening camera and starting preview
    private HandlerThread mHandlerThread;
    private Handler mHandler;

    // UI handler for callback
    private Handler mUIHandler;

    // camera info
    private final static int INVALID_CAMERA_ID = -1;
    private Camera mCamera;
    private int mCameraId = INVALID_CAMERA_ID;
    private boolean mIsBackCamera;
    private CameraOpenCallback mCameraOpenCallback;

    // preview info
    private SurfaceHolder mSurfaceHolder;
    private IPreviewCallback mPreviewCallback;


    public CameraWrapperImpl() {
    }

    /**
     * callback for frame data of preview
     *
     * @param data
     * @param camera
     */
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (checkNotNull(mPreviewCallback)) {
            mPreviewCallback.onPreviewFrame(data, camera);
        }
    }

    /**
     * Open Camera asynchronously
     */
    @Override
    public void openCamera(boolean isBackCamera, Context context, CameraOpenCallback callback) {
        Log.d(TAG, "openCamera()...back camera:" + isBackCamera);

        // init worker thread
        initEventLooper(context);

        // save params
        mIsBackCamera = isBackCamera;
        mCameraOpenCallback = callback;

        // obtain message
        Message msg = Message.obtain(mHandler, EVENT_OPEN_CAMERA);
        msg.sendToTarget();
    }

    /**
     * Start Preview
     *
     * @param surfaceHolder
     */
    @Override
    public void startPreview(SurfaceHolder surfaceHolder) {
        Log.d(TAG, "startPreview()...");

        // save holder
        mSurfaceHolder = surfaceHolder;

        // obtain message
        Message msg = Message.obtain(mHandler, EVENT_START_PREVIEW);
        msg.sendToTarget();
    }

    /**
     * Begin to detect and receive frame data of preview
     *
     * @param callback receive frame data of preview
     */
    @Override
    public void startDetect(IPreviewCallback callback) {
        mPreviewCallback = callback;
    }

    /**
     * Stop detecting，
     */
    @Override
    public void stopDetect() {
        mPreviewCallback = null;
    }

    /**
     * Stop preview
     */
    @Override
    public void stopPreview() {
        Log.d(TAG, "stopPreview()...");
        if (checkNotNull(mCamera)) {
            mCamera.stopPreview();
        }
    }

    /**
     * 关闭camera
     */
    @Override
    public void closeCamera() {
        Log.d(TAG, "closeCamera()...");
        if (checkNotNull(mCamera)) {
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
        }
        // reset camera info
        resetPreviewInfo();

        // exit event looper
        exitEventLooper();
    }

    public int mDisplayOrient;

    /**
     * Set Orientation of Display
     *
     * @param angle 0~360
     */
    @Override
    public void setDisplayOrientation(int angle) {
        mDisplayOrient = angle;
        if (checkNotNull(mCamera)) {
            mCamera.setDisplayOrientation(angle);
        }
    }

    @Override
    public int getAngle() {
        if (mDisplayOrient == 0) {
            return Constants.ORIENTATION_0;
        } else {
            return Constants.ORIENTATION_90;
        }
    }

    // init event thread
    private void initEventLooper(Context context) {
        Log.d(TAG, "initEventLooper()...");
        mHandlerThread = new HandlerThread(TAG);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.d(TAG, "handleMessage()..." + msg);
                switch (msg.what) {
                    case EVENT_OPEN_CAMERA:
                        handleOpenCamera();
                        break;
                    case EVENT_START_PREVIEW:
                        handleStartPreview();
                        break;
                    default:
                        Log.d(TAG, "Unsupport event type");
                        break;
                }
                return false;
            }
        });

        mUIHandler = new Handler(context.getMainLooper());
    }

    private void exitEventLooper() {
        Log.d(TAG, "exitEventLooper()...");
        if (checkNotNull(mUIHandler)) {
            mUIHandler.removeCallbacksAndMessages(null);
        }
        if (checkNotNull(mHandler)) {
            mHandler.removeCallbacksAndMessages(null);
        }
        if (checkNotNull(mHandlerThread)) {
            mHandlerThread.quit();
        }
        mHandlerThread = null;
        mHandler = null;
        mUIHandler = null;
    }

    /**
     * Open Camera
     */
    // handle for opening Camera
    private void handleOpenCamera() {
        Log.d(TAG, "handleOpenCamera()...");

        // select back or front camera
        mCameraId = selectBackOrFrontCamera();
        Log.d(TAG, "handleOpenCamera()...selected=" + mCameraId + "mIsBackCamera" + mIsBackCamera);

        if (INVALID_CAMERA_ID == mCameraId) {
            Log.e(TAG, "camera id is invalid, return!");
            callbackToCaller(Result.RESULT_FAILED);
            return;
        }
        // open camera
        try {
            mCamera = Camera.open(mCameraId);

            // config camera info
            configCameraParams();

            // set preview call
            setPreviewCallback(this);

            // open successfully
            callbackToCaller(Result.RESULT_SUCCESS);
            return;
        } catch (Exception e) {
            Log.e(TAG, "failed to open camera first! cameraid=" + mCameraId);

            e.printStackTrace();
        }

        //try
        for (int camIdx = 0; camIdx < Camera.getNumberOfCameras(); ++camIdx) {
            try {
                mCameraId = camIdx;
                mCamera = Camera.open(camIdx);

                // config camera info
                configCameraParams();

                // set preview call
                setPreviewCallback(this);

                // open successfully
                callbackToCaller(Result.RESULT_SUCCESS);
                return;
            } catch (Exception e) {
                Log.e(TAG, "failed to open camera try! cameraid=" + mCameraId);

                e.printStackTrace();
            }
        }
        callbackToCaller(Result.RESULT_FAILED);
    }

    /**
     * Start Preview
     */
    private void handleStartPreview() {
        Log.d(TAG, "handleStartPreview()...");
        if (checkNotNull(mCamera)) {
            try {
                mCamera.setPreviewDisplay(mSurfaceHolder);
                mCamera.startPreview();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


    }

    private void callbackToCaller(final int result) {
        Log.d(TAG, "callbackToCaller...");
        if (checkNotNull(mUIHandler)) {
            mUIHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (checkNotNull(mCameraOpenCallback)) {
                        switch (result) {
                            case Result.RESULT_SUCCESS:
                                mCameraOpenCallback.onOpenSuccess();
                                break;
                            case Result.RESULT_DISCONNECTED:
                                mCameraOpenCallback.onDisconnected();
                                break;
                            case Result.RESULT_FAILED:
                                mCameraOpenCallback.onOpenFailed();
                                break;
                            default:
                                break;
                        }
                    }
                }
            });
        }
    }

    // select back or front camera
    private int selectBackOrFrontCamera() {

        // get Camera numbers
        int cameraNumber = Camera.getNumberOfCameras();

        // select camera based on isBackCamera
        int cameraId = INVALID_CAMERA_ID;
        Log.d(TAG, "selectBackOrFrontCamera()...=" + cameraNumber);

        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        for (int index = 0; index < cameraNumber; index++) {
            Camera.getCameraInfo(index, cameraInfo);
            Log.d(TAG, "selectBackOrFrontCamera()...index=" + index + "infoface=" + cameraInfo.facing);

            if (mIsBackCamera && // back camera
                    cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                cameraId = index;
                Log.d(TAG, "selectBackOrFrontCamera()...back=" + index);

                break;
            } else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { // front camera
                Log.d(TAG, "selectBackOrFrontCamera()...font=" + index);

                cameraId = index;
                break;
            } else if (cameraNumber == 1) {  //single camera  一般为0
                Log.d(TAG, "selectBackOrFrontCamera()...single=" + index);
                cameraId = index;
            }
        }
        //spacial
        if (cameraNumber == 3) {
            Log.d(TAG, "selectBackOrFrontCamera: ...spacial cameraNumber3");
            cameraId = 0;
        } else if (cameraNumber == 0) {
            Log.d(TAG, "selectBackOrFrontCamera: ...spacial cameraNumber0 ");
            cameraId = 0;
        }
        return cameraId;
    }


    // config params of camera
    private void configCameraParams() {
        try {
            if (checkNotNull(mCamera)) {
                Camera.Parameters params = mCamera.getParameters();
                Camera.Size bestPreviewSize = calBestPreviewSize(
                        params, mWidth, mHeight);
                this.mWidth = bestPreviewSize.width;
                this.mHeight = bestPreviewSize.height;
                // set preview size
                params.setPreviewSize(this.mWidth, this.mHeight);
                // set data format
                params.setPreviewFormat(ImageFormat.NV21);

                Log.w(TAG, "Angle==" + mAngle + "|" + this.mWidth + "|" + this.mHeight);
                mCamera.setParameters(params);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // set callback of preview
    private void setPreviewCallback(Camera.PreviewCallback callBack) {
        if (null != mCamera) {
            mCamera.setPreviewCallback(callBack);
        }
    }

    // check if Camera is null
    private boolean checkNotNull(Object object) {
        return (null != object);
    }

    // reset preview info
    private void resetPreviewInfo() {
        mCameraId = INVALID_CAMERA_ID;
        mSurfaceHolder = null;
        mCameraOpenCallback = null;

        mPreviewCallback = null;
        mIsBackCamera = false;
    }

    /**
     * 通过传入的宽高算出最接近于宽高值的相机大小
     */
    private Camera.Size calBestPreviewSize(Camera.Parameters camPara, final int width, final int
            height) {
        List<Camera.Size> allSupportedSize = camPara.getSupportedPreviewSizes();
        ArrayList<Camera.Size> widthLargerSize = new ArrayList<>();
        for (Camera.Size tmpSize : allSupportedSize) {
            Log.w(TAG, "tmpSize.width===" + tmpSize.width
                    + ", tmpSize.height===" + tmpSize.height);
            if (tmpSize.width > tmpSize.height) {
                widthLargerSize.add(tmpSize);
            }
        }

        Collections.sort(widthLargerSize, new Comparator<Camera.Size>() {
            @Override
            public int compare(Camera.Size lhs, Camera.Size rhs) {
                int off_one = Math.abs(lhs.width * lhs.height - width * height);
                int off_two = Math.abs(rhs.width * rhs.height - width * height);
                return off_one - off_two;
            }
        });

        Log.w(TAG, "best matched size:" + widthLargerSize.get(0).width + ":" + widthLargerSize.get(0).height);
        return widthLargerSize.get(0);
    }

    // 获取照相机旋转角度
    private int getCameraAngle(int windowDefaultRotation) {
        int rotateAngle;
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraId, info);
        int degrees = 0;
        switch (windowDefaultRotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            rotateAngle = (info.orientation + degrees) % 360;
            rotateAngle = (360 - rotateAngle) % 360; // compensate the mirror
        } else { // back-facing
            rotateAngle = (info.orientation - degrees + 360) % 360;
        }
        return rotateAngle;
    }
}
